package edu.towson.cosc716.models;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import edu.towson.cosc716.Commands.ClearTableCmd;
import edu.towson.cosc716.Commands.GetMenuCmd;
import edu.towson.cosc716.Commands.KitchenOrderPreparedCmd;
import edu.towson.cosc716.Commands.NextKitchenOrderCmd;
import edu.towson.cosc716.Commands.PayBillCmd;
import edu.towson.cosc716.Commands.PlaceOrderCmd;
import edu.towson.cosc716.Commands.RequestTableCmd;
import edu.towson.cosc716.Commands.ReviewMenuItemCmd;
import edu.towson.cosc716.exceptions.OrderException;
import edu.towson.cosc716.interfaces.Command;
import edu.towson.cosc716.interfaces.ObservableInterface;
import edu.towson.cosc716.interfaces.ObserverInterface;

// TODO: Auto-generated Javadoc
/**
 * The Class Invoker.
 */
public class Invoker implements ObserverInterface, ObservableInterface {
	
	/** The information aggregator. */
	private InformationAggregator informationAggregator;
	
	/** The observers. */
	private List<ObserverInterface> observers = new ArrayList<ObserverInterface>();

	/**
	 * Instantiates a new invoker.
	 *
	 * @param informationAggregator the information aggregator
	 */
	public Invoker(InformationAggregator informationAggregator){
		this.informationAggregator = informationAggregator;		
	}
	
	/**
	 * Process request.
	 *
	 * @param requestedOperation the requested operation
	 * @param arguments the arguments
	 * @return the object
	 * @throws Exception the exception
	 */
	public Object processRequest(String requestedOperation, List<Object> arguments) throws Exception {
		
		// Result is the object to be returned.
		Object result;
		int count;
		
/*	Huge F-ing if/else statement to process operations. Add on else if statements for each operation.  
*	Not pretty but effective and makes adding easy.  The final else statement is a default catch.
*/
		if (requestedOperation.equals(Constants.Operations.GET_MENU)) {
			//  Two argument needed.  Menu filter type and parameter for filter.
			count = 2;
			checkArgumentCount(count, arguments);
			Command command = new GetMenuCmd((String)arguments.get(0), (String)arguments.get(1), informationAggregator);
			result = command.execute();
			return result;
		//} else if (requestedOperation.equals(Constants.Operations)) { <- is a template.
		} else if (requestedOperation.equals(Constants.Operations.SET_TABLE_STATUS)) {
			count = 2;
			checkArgumentCount(count, arguments);
			//  Need to write logic
			
			return null;
		} else if (requestedOperation.equals(Constants.Operations.REQUEST_TABLE)) {
			count = 2;
			checkArgumentCount(count, arguments);
			Command command = new RequestTableCmd((String)arguments.get(0), (Integer)arguments.get(1), informationAggregator);
			result = command.execute();
			return result;
		} else if (requestedOperation.equals(Constants.Operations.PLACE_ORDER)) {
			count = 2;
			checkArgumentCount(count, arguments);
			Command command = new PlaceOrderCmd((Integer)arguments.get(0), (Integer)arguments.get(1), informationAggregator);
			result = command.execute();
			if (result == null) {
				throw new OrderException(Constants.ExceptionMessages.ORDER_MENU_ITEM_NULL, new Exception());
			}
			return result;			
		} else if (requestedOperation.equals(Constants.Operations.SET_TABLE_CLEAR)) {
			count = 1;
			checkArgumentCount(count, arguments);
			Command command = new ClearTableCmd((Integer)arguments.get(0), informationAggregator);
			result = command.execute();
			if (result == null) {
				throw new Exception("Table is null.");
			}
			return result;
		} else if (requestedOperation.equals(Constants.Operations.NEXT_KITCHEN_QUEUE_MENU_ITEM)) {
			count = 1;
			checkArgumentCount(count, arguments);
			Command command = new NextKitchenOrderCmd((Integer)arguments.get(0), informationAggregator);
			result = command.execute();
			updateAll();
			return result;
		} else if (requestedOperation.equals(Constants.Operations.SET_ORDER_PREPARED)) {
			count = 1;
			checkArgumentCount(count, arguments);
			Command command = new KitchenOrderPreparedCmd((Integer)arguments.get(0), informationAggregator);
			result= command.execute();
			updateAll();
			if (result == null) {
				throw new Exception("Order is null.");
			}
			return result;
		} else if (requestedOperation.equals(Constants.Operations.REVIEW_MENU_ITEM)) {
			count =2;
			checkArgumentCount(count, arguments);
			Command command = new ReviewMenuItemCmd((Integer)arguments.get(0), (Integer)arguments.get(1), informationAggregator);
			Object results = command.execute();
			return results;
		} else if (requestedOperation.equals(Constants.Operations.GET_TAB)) {
			count = 1;
			checkArgumentCount(count, arguments);
			Command command = new PayBillCmd((Integer)arguments.get(0), informationAggregator);
			Object results = command.execute();
			return null;
		} else {
			return null;
		}		
	}
	
	/**
	 * Check argument count.
	 *
	 * @param count the count
	 * @param arguments the arguments
	 * @throws Exception the exception
	 */
	private void checkArgumentCount(int count, List<Object> arguments) throws Exception {
		if (arguments.size() != count) {
			throw new Exception("Incorrect argument count.");
		} 
	}

	//  Figure out why I did this
	/* (non-Javadoc)
	 * @see edu.towson.cosc716.interfaces.ObserverInterface#update(edu.towson.cosc716.interfaces.ObservableInterface)
	 */
	@Override
	public void update(ObservableInterface o) {
		List<Object> arguments = new ArrayList<Object>();
		if (o.getType().equals(Constants.ObjectTypes.CHEF)) {
			Chef chef = (Chef) o;
			arguments.add(chef.getChefId());
			try {
				Object result = processRequest(Constants.Operations.NEXT_KITCHEN_QUEUE_MENU_ITEM, arguments);
			} catch (Exception e) {
				System.out.println("Exception from Chef calling invoker through obsever pattern.  Non-existant chef ID.");
			}
		}
		
	}
	
	/**
	 * Tick.
	 */
	public void tick() {
		// Kitchen time advance
		for (Map.Entry<Chef, Order> entry : informationAggregator.kitchen.entrySet()) {
			if (entry.getValue()  != null) {
				Order order = entry.getValue();
				int cookTime = order.getCookTime();
				cookTime--;
				order.setCookTime(cookTime);
				entry.setValue(order);
			}
		}
		
		
	}
	
	/**
	 * Update all.
	 */
	public void updateAll() {
		for (int i = 0; i < observers.size(); i++) {
			observers.get(i).update(this);
		}
	}

	/* (non-Javadoc)
	 * @see edu.towson.cosc716.interfaces.ObservableInterface#addObserver(edu.towson.cosc716.interfaces.ObserverInterface)
	 */
	@Override
	public void addObserver(ObserverInterface o) {
		observers.add(o);
	}

	/* (non-Javadoc)
	 * @see edu.towson.cosc716.interfaces.ObservableInterface#removeObserver(edu.towson.cosc716.interfaces.ObserverInterface)
	 */
	@Override
	public void removeObserver(ObserverInterface o) {
		observers.remove(o);
		
	}

	/* (non-Javadoc)
	 * @see edu.towson.cosc716.interfaces.ObservableInterface#getType()
	 */
	@Override
	public String getType() {
		return Constants.ObjectTypes.INVOKER;
	}
	
	
}
